﻿using System;
using System.Diagnostics;
using Emgu.CV.Structure;
using Emgu.CV;
using TestEmguCV.Objects;

namespace TestEmguCV.Recognizer
{
    public class ObjectRecognizer
    {
        public Image<Gray,Single>[] DataImages { get; set; }
        public Image<Gray, Single> AvgImage { get; set; }
        public Matrix<float>[] DataValues { get; set; }
        public double DistanceThreshold { get; set; }
        public string[] IDs { get; set; }

        public ObjectRecognizer(Image<Gray, Byte>[] images, string[] ids, double distanceThreshold,MCvTermCriteria termCrit)
        {
            Debug.Assert(images.Length == ids.Length, "The number of images should equals the number of labels");
             Debug.Assert(distanceThreshold >= 0.0, "Eigen-distance threshold should always >= 0.0");

             CalcEigenObjects(this,images, termCrit, DataImages, AvgImage);

             /*
             _avgImage.SerializationCompressionRatio = 9;

             foreach (Image<Gray, Single> img in _eigenImages)
                 //Set the compression ration to best compression. The serialized object can therefore save spaces
                 img.SerializationCompressionRatio = 9;
             */

             DataValues = Array.ConvertAll<Image<Gray, Byte>, Matrix<float>>(images,
                 delegate(Image<Gray, Byte> img)
                 {
                     return new Matrix<float>(EigenDecomposite(img, DataImages, AvgImage));
                 });

             IDs = ids;

             DistanceThreshold = distanceThreshold;
        }

        #region staticMethod
        public static void CalcEigenObjects(ObjectRecognizer objr,Image<Gray, Byte>[] trainingImages,MCvTermCriteria termCrit, Image<Gray, Single>[] eigenImages, Image<Gray, Single> avg)
        {
            int width = trainingImages[0].Width;
            int height = trainingImages[0].Height;

            IntPtr[] inObjs = Array.ConvertAll<Image<Gray, Byte>, IntPtr>(trainingImages, delegate(Image<Gray, Byte> img) { return img.Ptr; });

            if (termCrit.max_iter <= 0 || termCrit.max_iter > trainingImages.Length)
                termCrit.max_iter = trainingImages.Length;

            int maxEigenObjs = termCrit.max_iter;

            #region initialize eigen images
            eigenImages = new Image<Gray, float>[maxEigenObjs];
            for (int i = 0; i < eigenImages.Length; i++)
                eigenImages[i] = new Image<Gray, float>(width, height);
            IntPtr[] eigObjs = Array.ConvertAll<Image<Gray, Single>, IntPtr>(eigenImages, delegate(Image<Gray, Single> img) { return img.Ptr; });
            #endregion

            avg = new Image<Gray, Single>(width, height);

            CvInvoke.cvCalcEigenObjects(
                inObjs,
                ref termCrit,
                eigObjs,
                null,
                avg.Ptr);
            objr.DataImages = eigenImages;
            objr.AvgImage = avg;
        }
        public static float[] EigenDecomposite(Image<Gray, Byte> src, Image<Gray, Single>[] eigenImages, Image<Gray, Single> avg)
        {
            return CvInvoke.cvEigenDecomposite(
                src.Ptr,
                Array.ConvertAll<Image<Gray, Single>, IntPtr>(eigenImages, delegate(Image<Gray, Single> img) { return img.Ptr; }),
                avg.Ptr);
        }
        #endregion
        
        public float[] GetEigenDistances(Image<Gray, Byte> image)
        {
            using (Matrix<float> eigenValue = new Matrix<float>(EigenDecomposite(image, DataImages, AvgImage)))
                return Array.ConvertAll<Matrix<float>, float>(DataValues,
                    delegate(Matrix<float> eigenValueI)
                    {
                        return (float)CvInvoke.cvNorm(eigenValue.Ptr, eigenValueI.Ptr, Emgu.CV.CvEnum.NORM_TYPE.CV_L2, IntPtr.Zero);
                    });
        }
        public void FindMostSimilarObject(Image<Gray, Byte> image, out int index, out float eigenDistance)
        {
            float[] dist = GetEigenDistances(image);
            Console.WriteLine("Distance Array size: "+dist.Length+"\n");
            for (int i = 0; i < dist.Length; i++)
            {
                Console.WriteLine(i+": "+dist[i]+" ");
            }
            Console.WriteLine("\n");
            index = 0;
            eigenDistance = dist[0];
            for (int i = 1; i < dist.Length; i++)
            {
                if (dist[i] < eigenDistance)
                {
                    index = i;
                    eigenDistance = dist[i];
                }
            }
            Console.WriteLine("eigenDistance:" + eigenDistance);
        }
        public string Recognize(Image<Gray, Byte> image)
        {
            int index;
            float eigenDistance;
            FindMostSimilarObject(image, out index, out eigenDistance);
            return (DistanceThreshold <= 0 || eigenDistance < DistanceThreshold) ? IDs[index] : "";
        }
    }
}
